namespace Gma.QrCodeNet.Encoding.ReedSolomon;

/// <summary>
/// Description of GaloisField256.
/// </summary>
internal sealed class GaloisField256
{
	internal GaloisField256(int primitive)
	{
		AntiLogTable = new int[256];
		LogTable = new int[256];

		Primitive = primitive;

		int gfx = 1;

		// Power cycle is from 0 to 254. 2^255 = 1 = 2^0
		// Value cycle is from 1 to 255. Thus there should not have Log(0).
		for (int powers = 0; powers < 256; powers++)
		{
			AntiLogTable[powers] = gfx;
			if (powers != 255)
			{
				LogTable[gfx] = powers;
			}

			gfx <<= 1; // gfx = gfx * 2 where alpha is 2.

			if (gfx > 255)
			{
				gfx ^= primitive;
			}
		}
	}

	private int[] AntiLogTable { get; }
	private int[] LogTable { get; }

	internal int Primitive { get; }

	internal static GaloisField256 QRCodeGaloisField => new(QRCodeConstantVariable.QRCodePrimitive);

	/// <returns>
	/// Powers of a in GF table. Where a = 2
	/// </returns>
	internal int Exponent(int powersOfa) => AntiLogTable[powersOfa];

	/// <returns>
	/// Log (power of a) in GF table. Where a = 2
	/// </returns>
	internal int Log(int gfValue)
	{
		if (gfValue == 0)
		{
			throw new ArgumentException("GaloisField value will not be equal to 0, Log method.");
		}

		return LogTable[gfValue];
	}

	internal int Inverse(int gfValue)
	{
		if (gfValue == 0)
		{
			throw new ArgumentException("GaloisField value will not be equal to 0, Inverse method.");
		}

		return Exponent(255 - Log(gfValue));
	}

	internal int Addition(int gfValueA, int gfValueB) => gfValueA ^ gfValueB;

	internal int Subtraction(int gfValueA, int gfValueB) => Addition(gfValueA, gfValueB); // Subtraction is same as addition.

	/// <returns>
	/// Product of two values.
	/// In other words. a multiply b
	/// </returns>
	internal int Product(int gfValueA, int gfValueB)
	{
		if (gfValueA == 0 || gfValueB == 0)
		{
			return 0;
		}
		if (gfValueA == 1)
		{
			return gfValueB;
		}
		if (gfValueB == 1)
		{
			return gfValueA;
		}

		return Exponent((Log(gfValueA) + Log(gfValueB)) % 255);
	}

	/// <returns>
	/// Quotient of two values.
	/// In other words. a divided b
	/// </returns>
	internal int Quotient(int gfValueA, int gfValueB)
	{
		if (gfValueA == 0)
		{
			return 0;
		}

		if (gfValueB == 0)
		{
			throw new ArgumentException($"{nameof(gfValueB)} cannot be zero.");
		}

		if (gfValueB == 1)
		{
			return gfValueA;
		}

		return Exponent(Math.Abs(Log(gfValueA) - Log(gfValueB)) % 255);
	}
}
